/*
 * Copyright (C) 2011 JiangHongTiao <jjurco.sk_gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package sk.lieskove301.jianghongtiao.liaad.prisoner.strategy;

import cern.jet.random.Normal;
import cern.jet.random.engine.MersenneTwister;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import sk.lieskove301.jianghongtiao.liaad.prisoner.agent.Agent;
import sk.lieskove301.jianghongtiao.liaad.prisoner.agent.AgentDescComparator;
import sk.lieskove301.jianghongtiao.liaad.prisoner.agent.RandomGenesAgent;
import sk.lieskove301.jianghongtiao.liaad.prisoner.ga.crossover.Crossover;
import sk.lieskove301.jianghongtiao.liaad.prisoner.ga.mutation.Mutation;
import sk.lieskove301.jianghongtiao.liaad.prisoner.ga.selection.Selection;
import sk.lieskove301.jianghongtiao.liaad.prisoner.gene.GeneEnum;
import sk.lieskove301.jianghongtiao.liaad.prisoner.gene.GeneFunction;
import sk.lieskove301.jianghongtiao.liaad.prisoner.memory.ForgettingEnum;
import sk.lieskove301.jianghongtiao.liaad.prisoner.payoff.PayoffValue;

/**
 * Date of create: May 24, 2011
 *
 * @author JiangHongTiao <jjurco.sk_gmail.com>
 * @version 2011.0524
 */
public class GeneticAlgorithmStrategy implements Strategy{
    
    private PayoffValue payoff = null;
    private ForgettingEnum[] forgetting = new ForgettingEnum[]{};
    private Random rand = new Random();
    
    private Selection selection = null;
    private Mutation mutation = null;
    private Crossover crossover = null;
    private GeneEnum[] possibleGenes = null;
    private Normal normal = null;
    private int numberOfAgents = 0;
    
    
    public List<Agent> incubator(int numberOfAgents) {
        this.numberOfAgents = numberOfAgents;
        return generateRandomAgents(numberOfAgents);
    }

    /**
     * generate random agents with random genetic code. Number of agents is divided 
     * by 4 and every type of agent is introduced: pessimistic/optimistic/inverted/normal
     * @param numberOfAgents number of agents to generate
     * @return number of generated agents is not the same as param <code>numberOfAgents</code>.
     * it is nearest lower number dividable by 4 without rest. Result is list of
     * agents generated randomly
     */
    private List<Agent> generateRandomAgents(int numberOfAgents) {
        List<Agent> result = new ArrayList<Agent>(numberOfAgents);
        int year = 0;
        for (int i = 0; i < numberOfAgents/4; i++) {
            Agent pesimistic = makeRandomAgent(year);
            Agent optimistic = makeRandomAgent(year);
            Agent inverseP = makeRandomAgent(year);
            Agent inverseO = makeRandomAgent(year);
            optimistic.setOptimistic(true);
            inverseP.setInvert(true);
            inverseO.setInvert(true);
            inverseO.setOptimistic(true);
            result.add(pesimistic);
            result.add(optimistic);
            result.add(inverseO);
            result.add(inverseP);
        }
        return result;
    }
    
    /**
     * get random agent from actual populationof agents
     * @param population actual population
     * @return randomly (based on distribution used) selected agent
     */
    private Agent getRandomAgent(List<Agent> population){
        if((population == null) || (population.isEmpty())){
            throw new IllegalArgumentException("Population is null or empty!");
        }
        int idx = (int)(GeneFunction.getUniformNumber(GeneEnum.StudentT)*population.size());
        
        return population.get(idx%population.size());
    }
    
    /**
     * pick different agent like previously picked
     * @param population population of agents 
     * @param picked already picked agent
     * @return randomly picked agent which differs from previously picked
     */
    private Agent pickDifferent(List<Agent> population, Agent picked){
        if((population == null) || (population.size() < 2)){
            throw new IllegalStateException("Number of agents in population is "
                    + "null or less than 2. Cannot pick different agent");
        }
        Agent result = getRandomAgent(population);
        while(result == picked){
            result = getRandomAgent(population);
        }
        return result;
    }

    public List<Agent> renewPopulation(List<Agent> population, int year) {
        if((population == null) || (population.isEmpty())){
            throw new IllegalArgumentException("Population is null or empty!");
        }
        //sort agents in the array based on their score. Better agents are on 
        //the beginneng of array
        Collections.sort(population, new AgentDescComparator());
        //full-fill population with new agents
        int half = (int) ((numberOfAgents-population.size())/3);
        List<Agent> result = new ArrayList<Agent>();
        for (int i = 0; i < half; i++) {
            //make mutants
            Agent mutant = getRandomAgent(population);
            result.add(mutation.modify(mutant, possibleGenes, forgetting));
            //make crossover
            Agent cross1 = getRandomAgent(population);
            Agent cross2 = pickDifferent(population, cross1);
            result.add(crossover.cross(cross1, cross2));
            //copy best
            Agent copy = getRandomAgent(population).copyAgent();
            result.add(copy);
        }
        return result;
    }

    public List<Agent> genocide(List<Agent> population) {
        //select successful agents and other agents will be removed
        List<Agent> selectBestAgents = selection.selectAgents(population);
        //copy actual population
        List<Agent> removeThisAgents = new ArrayList<Agent>(population);
        //remove from population selected best agents
        removeThisAgents.removeAll(selectBestAgents);
        //return all other agents (All\Best)
        return removeThisAgents;
    }

    public ForgettingEnum[] getForgetting() {
        return forgetting;
    }

    public void setForgetting(ForgettingEnum[] forgetting) {
        this.forgetting = forgetting;
    }

    public PayoffValue getPayoff() {
        return payoff;
    }

    public void setPayoff(PayoffValue payoff) {
        this.payoff = payoff;
    }

    public void agentsAmnesia(Agent agent) {
        agent.totalAmnesia();
    }

    private Agent makeRandomAgent(int bornYear) {
        //%only to be sure, not needed
        int randNum = rand.nextInt(forgetting.length)%forgetting.length;
        return new RandomGenesAgent(forgetting[randNum], 
                payoff, bornYear, possibleGenes);
    }

    public Crossover getCrossover() {
        return crossover;
    }

    public void setCrossover(Crossover crossover) {
        this.crossover = crossover;
    }

    public Mutation getMutation() {
        return mutation;
    }

    public void setMutation(Mutation mutation) {
        this.mutation = mutation;
    }

    public Selection getSelection() {
        return selection;
    }

    public void setSelection(Selection selection) {
        this.selection = selection;
    }

    public GeneEnum[] getPossibleGenes() {
        return possibleGenes;
    }

    public void setPossibleGenes(GeneEnum[] possibleGenes) {
        this.possibleGenes = possibleGenes;
    }
    
}
